/*
 * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
 * All rights reserved.
 *
 * This file is part of Harmattan SmashMiner OpenGL game application.
 * 
 * Harmattan SmashMiner OpenGL Example Application version 1.0.0
 *  
 * Latest update: 15.4.2011
 *
 * The Harmattan SmashMiner OpenGL example application demonstrates how to use 
 * the OpenGL ES in Harmattan devices.
 *
 * This example is provided as a starting point for 3rd party
 * developers (you) to ease the implementation of OpenGL based 
 * games. Ideas, parts of code or methodologies that this
 * example application uses can be freely used without restrictions.
 *
 * See file README.txt how to build and compile this example application
 * in the Harmattan SDK environment. 
 * 
 * See file INSTALL.txt to find out what is required to run this
 * application and how to install the application to the device or
 * alternatively in QEMU-emulator.
 *
 * The LICENSE.txt file explains the license this software is provided
 * with. A copy of the same license is included in this source file.
 * 
 */

/*
 * Copyright (C) 2011 by Nokia Corporation.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "soundengine.h"

#include <QFile>
#include <QDebug>
#include "wavfile.h"

#ifdef SMASHMINER_CONFIG_ENABLE_SOUND_ENGINE_QAUDIO
#include "soundengineqaudio.h"
#endif

#ifdef SMASHMINER_CONFIG_ENABLE_SOUND_ENGINE_PULSE_AUDIO
#include "soundenginepulseaudio.h"
#endif

#include "gameeffect.h"


/*!
    \class SoundEngine
    \brief The SoundEngine class plays the game sound effects by using the preferred sound engine implementation.

    The SoundEngine class is used to play the game sound effects. SoundEngine is a generic class without the actual playback implementation.
    SoundEngine loads WAV soundfiles and instantiatates the sound engine that is responsible for the playback.

    \par Tutorials

    \ref replacingSoundEffect

    \ref addNewSoundEffect

    \sa GameEngine SoundEnginePulseAudio SoundEngineQAudio
*/


/*! \page replacingSoundEffect Replacing the Game Sound Effect

    Here are the instructions for replacing the existing game sound effect with a new one.
    The sound effect replaced here is the explosion sound played when the player crashes the miner.

    \section createSoundEffectFile Creating a Sound Effect File

    First you must have a WAV file with a correct audio format and volume level. The correct audio format of the sound effects is a 16-bit PCM stereo with 8 kHZ sampling rate.
    Sound effects can be converted using a <a href="http://sox.sourceforge.net/">SOX</a> tool:
    \code
    $ sox explosion_orig.wav new_explosion.wav channels 2 rate 8k
    \endcode
    The volume level must be normalized so that every sound effect has the same output volume. <a href="http://normalize.nongnu.org/">Normalize</a> audio tool can be used for this:
    \code
    $ normalize-audio -bv *.wav
    \endcode

    \section addSoundEffectFileToResources Adding the Sound Effect File to Resources

    Next thing to do is to add the sound effect file to the resources so that it can be used at the game. The sound files are stored under the project folder to the \c ./sounds folder.

    Copy the file \c new_explosion.wav to \c ./sounds folder.
    Then the resource file smashminer.qrc must be edited. Add \c new_explosion.wav file to the resources under \c sounds resource prefix.

    Then add alias \c "explosion.wav" for the resource file.

    Now the resource file can be referenced from the source code.

    \section referenceSoundEffectFileFromSourceCode Referencing the New Sound Effect File from Source Code

    The sound effect files in resources are used from the SoundEngine constructor SoundEngine::SoundEngine. The loadFile() method is used to load the sound file from resources to the sound engine.

    Finally replace the resource reference to the old file:
    \code
    ...
    loadFile(SoundExplosion, ":/sounds/explosion.wav");
    ...
    \endcode
    With the reference to the new file:
    \code
    ...
    loadFile(SoundExplosion, ":/sounds/new_explosion.wav");
    ...
    \endcode

    The old sound effect is replaced with the new one.
*/

/*! \page addNewSoundEffect Adding New Sound Effect to Game Event

    Here are the instructions for adding a new game sound effect to a particular game event.
    The bomb blast sound is used as an example.

    \section createSoundFileAndAddToResources Creating the File and Adding the File to Resources

    When adding a new sound effect you must have a new sound file and the file must be added to resources.
    See how to do this in section \ref createSoundEffectFile.

    The created sound file is added to the resources.
    See how to do this from section \ref addSoundEffectFileToResources.

    \section useSoundEffectFromGameEngine Using the Sound Effect from the GameEngine

    Next thing is to add a new sound effect type. Type is added to the enum SoundEffectType:
    \code
    ...
        /// Blast sound
        SoundBlast = 3,
    ...
    \endcode
    Next the new sound effect must be loaded to SoundEngine. This is done at the SoundEngine constructor SoundEngine::SoundEngine.
    The SoundEngine::loadFile() method is used to load the file from the resources to the sound engine:
    \code
    ...
    loadFile(SoundBlast, ":/sounds/blast.wav");
    ...
    \endcode

    After this the new SoundBlast SoundEffect can be used through the GameEngine.
    The new sound effect is played by creating a new SoundEffect with a given SoundEffectType by using the GameEngine::newSoundEffect method.

    To the GameEngine::launchBomb method, new code needs to be added to play the SoundBlast effect:
    \code
    ...
    if (soundEngine != NULL)
        newSoundEffect(SoundBlast);
    ...
    \endcode

    Finally when the GameEffects are handled at the end of the GameEngine::cycleGameLoop using the method GameEngine::processNewEffects, the SoundEngine is used to play the SoundEffect.
*/


const qint64 BufferDurationUs = 10 * 1000000;
const qint16 PCMS16MaxValue = 32767;
const quint16 PCMS16MaxAmplitude = 32768; // because minimum is -32768

/*!
    Constructs an SoundEngine.
    Instatiates QAudio ar PulseAudio engine and loads WAV sound files.
    Attribute \a volumeLevel sets the initial volume from 0-10.
*/
SoundEngine::SoundEngine(int volumeLevel, QObject *parent) 
    : QObject(parent)
#ifdef SMASHMINER_CONFIG_ENABLE_SOUND_ENGINE_QAUDIO
    , soundEngine(new SoundEngineQAudio(this))
#elif defined SMASHMINER_CONFIG_ENABLE_SOUND_ENGINE_PULSE_AUDIO
    , soundEngine(SoundEnginePulseAudio::instance())
#else
    // soundEngine will remain NULL if no
    // sound engines are enabled in config.h
    , soundEngine(NULL)
#endif
{
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
    qDebug() << "SoundEngine::SoundEngine() setting_volume_level " << volumeLevel;
#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

#ifdef SMASHMINER_CONFIG_ENABLE_SOUND_ENGINE_PULSE_AUDIO
    soundEngine->setVolumeLevel(volumeLevel);
#else
    Q_UNUSED(volumeLevel);
#endif

    loadFile(SoundMenu, ":/sounds/swoosh.wav");
    loadFile(SoundCollision, ":/sounds/collision.wav");
    loadFile(SoundExplosion, ":/sounds/explosion.wav");
    loadFile(SoundBlast, ":/sounds/blast.wav");
    loadFile(SoundHighScore, ":/sounds/chime.wav");
    loadFile(SoundGameOver, ":/sounds/gameover.wav");
    loadFile(SoundCollect, ":/sounds/click.wav");
}

/*!
    Destructs the SoundEngine.
*/
SoundEngine::~SoundEngine()
{
#ifdef SMASHMINER_CONFIG_ENABLE_SOUND_ENGINE_PULSE_AUDIO
    delete soundEngine;
#endif
}

/*!
    Sets the volume level given at the attribute \a volumeLevel.  The \a volumeLevel can be between 0-10.
*/
void SoundEngine::setVolumeLevel(int volumeLevel)
{
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
    qDebug() << "SoundEngine::setVolumeLevel() setting_volume_level " << volumeLevel;
#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

#ifdef SMASHMINER_CONFIG_ENABLE_SOUND_ENGINE_PULSE_AUDIO
    soundEngine->setVolumeLevel(volumeLevel);
#else
    Q_UNUSED(volumeLevel);
#endif
}

/*!
  Loads WAV sound file \a fileName of given \a effectType to the data buffer.
*/
void SoundEngine::loadFile(SoundEffectType effectType, const QString &fileName)
{
    Q_ASSERT(!fileName.isEmpty());
    QFile *file = new QFile(fileName, this);
    file->setFileName(fileName);
    Q_ASSERT(file->exists());
    WavFile wavFile;
    if (file->open(QFile::ReadOnly)) {
        wavFile.readHeader(*file);
        if (isPCMS16LE(wavFile.format())) {
            QByteArray *buffer = initialize(*file, wavFile, effectType);
            if (buffer && soundEngine)
                soundEngine->addEffectBuffer(effectType, buffer);
        } else {
            qWarning() << "Audio format not supported: " << formatToString(wavFile.format());
        }
    } else {
        qWarning() << "Could not open file: " << fileName;
    }

    delete file;
    file = 0;
}

/*!
  Initializes sound engine with WAV sound file.
  Reads audio data and audio format and sets them to sound engine.
*/
QByteArray* SoundEngine::initialize(QFile &file, WavFile &wavFile, SoundEffectType effectType)
{
    QByteArray *buffer = NULL;
    QAudioFormat audioFormat;
    audioFormat = wavFile.format();

    if (soundEngine)
        soundEngine->setAudioFormat(audioFormat);
    const qint64 bufferLength = audioLength(audioFormat, BufferDurationUs);
    buffer = new QByteArray();
    buffer->resize(bufferLength);
    buffer->fill(0);
    const qint64 length = wavFile.readData(file, *buffer, audioFormat);
    if (length && soundEngine) {
        soundEngine->setSoundLength(effectType, length);
        soundEngine->setAudioFormat(audioFormat);
    } else {
        delete buffer;
    }

    return buffer;
}

/*!
  Convets audio format to QString.
*/
QString SoundEngine::formatToString(const QAudioFormat &format)
{
    QString result;

    if (QAudioFormat() != format) {
        if (format.codec() == "audio/pcm") {
            Q_ASSERT(format.sampleType() == QAudioFormat::SignedInt);

            const QString formatEndian = (format.byteOrder() == QAudioFormat::LittleEndian)
                ?   QString("LE") : QString("BE");

            QString formatType;
            switch (format.sampleType()) {
            case QAudioFormat::SignedInt:
                formatType = "signed";
                break;
            case QAudioFormat::UnSignedInt:
                formatType = "unsigned";
                break;
            case QAudioFormat::Float:
                formatType = "float";
                break;
            case QAudioFormat::Unknown:
                formatType = "unknown";
                break;
            }

            QString formatChannels = QString("%1 channels").arg(format.channels());
            switch (format.channels()) {
            case 1:
                formatChannels = "mono";
                break;
            case 2:
                formatChannels = "stereo";
                break;
            }

            result = QString("%1 Hz %2 bit %3 %4 %5")
                .arg(format.frequency())
                .arg(format.sampleSize())
                .arg(formatType)
                .arg(formatEndian)
                .arg(formatChannels);
        } else {
            result = format.codec();
        }
    }

    return result;
}

/*!
    Creates new SoundEffect that plays the type of sound effect given at the attribute \a type. The new effect stores current \a frame and \a timeStamp. Returns \returns SoundEffect. The ownership is transferred. \sa ::SoundEffectType.
*/
SoundEffect *SoundEngine::createSoundEffect(SoundEffectType type, int frame, FPtype timeStamp)
{
    SoundEffect *newEffect = new SoundEffect(frame, timeStamp, type);
    QObject::connect(newEffect, SIGNAL(play(SoundEffectType)),
                     this, SLOT(play(SoundEffectType)));
    return newEffect;
}

/*!
    Plays the sound effect given at the attribute \a soundEffect. \sa ::SoundEffectType.
*/
void SoundEngine::play(SoundEffectType soundEffect)
{
    if (soundEngine)
        soundEngine->play(soundEffect);
}

/*!
    Plays the ::SoundEffectType::SoundMenu sound effect. \sa ::SoundEffectType.
*/
void SoundEngine::playMenuSound() {
    if (soundEngine)
        soundEngine->play(SoundMenu);
}

qint64 SoundEngine::audioDuration(const QAudioFormat &format, qint64 bytes)
{
    return (bytes * 1000000) 
        / (format.frequency() * format.channels() * (format.sampleSize() / 8));
}

qint64 SoundEngine::audioLength(const QAudioFormat &format, qint64 microSeconds)
{
   return (format.frequency() * format.channels() * (format.sampleSize() / 8))
       * microSeconds / 1000000;
}

bool SoundEngine::isPCM(const QAudioFormat &format)
{
    return (format.codec() == "audio/pcm");
}

bool SoundEngine::isPCMS16LE(const QAudioFormat &format)
{
    return (isPCM(format) 
        && format.sampleType() == QAudioFormat::SignedInt 
        && format.sampleSize() == 16 
        && format.byteOrder() == QAudioFormat::LittleEndian);
}
